/**
 * @created by kongqf on 2019-08-12
 * @updated by
 * @description 数据监控
 */
(function (yufp, window, factory) {
  var exports = factory(yufp, window, window.document);
  if (typeof define === 'function') {
    define(exports);
  }
  window.yufp.monitor = exports;
}(yufp, window, function (yufp, window, document) {
  // 公共变量判断状态
  var ROUTER_TYPE_BEFORE = 'ROUTER-BEFORE';
  var ROUTER_TYPE_MOUNT = 'ROUTER-MOUNT';
  var ROUTER_TYPE_READY = 'ROUTER-READY';
  var ROUTER_TYPE_UNMOUNT = 'ROUTER-UNMOUNT';
  // var ROUTER_TYPE_DESTROY = 'ROUTER-DESTROY';
  var AJAX_TYPE_BEFORE = 'AJAX-BEFORE';
  var AJAX_TYPE_AFTER = 'AJAX-AFTER';
  var AJAX_TYPE_EXCEPTION = 'AJAX-EXCEPTION';
  var GUID_PREFIX = 'yufpm'; // 生成随机号前缀
  var SYSTEM_PID = yufp.settings.SYSTEM_PID; // 全局pid值，每个系统修改此处
  var _API_WHITE_LIST = [/webmonitordata/]; // 针对API拦截的白名单,正则表达式

  var _BROWSER_FINGERPRINT = ''; // 浏览器指纹
  var _CLIENT_IP = ''; // 客户端IP
  var _ROUTER_TRACEID = getTraceId(); // 路由跟踪ID
  var _ROUTER_BEGINTIME = new Date().getTime(); // 路由开始时间
  var _RESOURCE_INDEX = 0; // 资源列表数组长度指标
  var _POST_LOGS_LENGTH = 20; // 上传日志长度
  var _POST_LOGS_PATCHID = ''; // 日志批传key值
  var _POST_LOGS_TEMP = {}; // 日志批传日志数组

  var interavalId = null;
  /**
   * 会话信息对象
   * @constructor
   */
  function Monitor () {
    // 存储监控信息
    this._logs = [];
    var CLIENTID = getClientId();
    var _options = {
      delayed: yufp.settings.delayed,
      maxLength: 20,
      updateUrl: backend.msmFrontWebService + '/api/monitor/webmonitordata/',
      TEMP_LOGS: {},
      // 必填的公共字段
      COMMON_DATA: {
        // 系统全局ID，在index.html中配置死值
        pid: SYSTEM_PID,
        // 文件/api加载状态
        code: 200,
        ip: '', // 客户端IP地址
        // 系统每次加载全局ID，每次刷新都新生成
        clientId: CLIENTID,
        // 数据类型
        dataType: 'otherOpen',
        ispv: false, // 标注是否一个独立的pv数据
        // 耗时
        costTime: '',
        // 当前请求地址
        url: '',
        isError: false,
        errorJsUrl: '',
        // // 请求状态
        // status: '',
        'logTimestamp': '', // 时间戳
        // 登录用户ID，从yufp.session中获取
        userId: '',
        // 异常信息
        // exceptionInfo: '',
        // 错误类型
        errorType: '',
        // 错误信息
        errorInfo: '',
        // 所在页面位置，路由页面路径
        // pagelocation: '',
        // 错误信息所在行数
        errorRow: '',
        // 错误信息所在列数
        errorCol: '',
        // 详细错误信息
        errorDetail: '',
        // 新增字段 by liucheng3 2019-10-23
        // 首次渲染耗时
        resLoadTime: 0,
        // 首次可交互耗时
        interactiveTime: 0,
        // dom加载耗时
        domDoneTime: 0,
        // 页面完全加载耗时
        pageDoneTime: 0
      }
    };
    // 请求信息，存储请求地址，状态，请求发起、获得响应/触发异常时的时间
    _options['requestType'] = {};
    _options['requestType'][AJAX_TYPE_BEFORE] = {};
    _options['requestType'][AJAX_TYPE_EXCEPTION] = {};
    _options['requestType'][AJAX_TYPE_AFTER] = {};

    yufp.extend(this, _options);
    this.addInterceptor();
  };
  /**
   * 初始化对象
   * @param {Object} setting 初始化配置信息
   */
  Monitor.prototype.init = function (setting) {
    var _this = this;
    this.COMMON_DATA = yufp.extend(this.COMMON_DATA, getStaticData());
    getIp.call(this);
    // 解析window.performance并形成对应日志
    setTimeout(function () {
      _this.outPutData(window.performance);
      _this.analysisResourceData();
    }, 5000);
    clearInterval(interavalId);
    interavalId = setInterval(function () {
      _this.patchUploadLogs();
    }, this.delayed);
    this.getInitParam();
  };

  /**
   * 添加拦截器
   * 1、添加service过滤器
   * 2、添加router过滤器
   * 3、添加window.onerror错误拦截器
   * 4、添加errorHandler错误拦截器
   * 5、代理yufp.logger.error拦截[yufp.core.min中try/catch错误外部无法捕获,只在yufp.logger.error中处理了]
   */
  Monitor.prototype.addInterceptor = function () {
    // 拦截错误日志打印
    var _oldError = yufp.logger.error;
    yufp.logger.error = function () {
      _oldError.apply(yufp.logger, arguments);
      var args = yufp.args2Arr(arguments);
      args.forEach(function (arg) {
        if (Object.prototype.toString.call(arg) === '[object Error]') {
          yufp.monitor.pushLogs(errorAnalysis(arg, {}));
        }
      });
    };

    var _oldMenuItemClick = yufp.frame.menuItemClick;
    yufp.frame.menuItemClick = function (menuId) {
      _oldMenuItemClick.apply(yufp.frame, arguments);
      // 激活标签重新生成traceid
      _ROUTER_TRACEID = getTraceId(); // 路由跟踪ID
      _ROUTER_BEGINTIME = new Date().getTime(); // 路由开始时间
    };
    /**
     * window.onerror绑定window的错误事件
     * 无法捕获的错误：ajax 404，图片资源404
     * 图片资源404需用window.addEventListner才可监听到
     * 可捕获错误：脚本错误
     * @param {String} message  错误信息
     * @param {String} source   出错的文件
     * @param {Long}  lineno   出错代码的行号
     * @param {Long}  colno  出错代码的列号
     * @param {Object} error    错误的详细信息，Anything
     */
    window.onerror = function (message, source, lineno, colno, error) {
      if (yufp.monitor) {
        var data = {};
        data = errorAnalysis(error, {});
        data['isError'] = true;
        data['errorMsg'] = message;
        data['errorRow'] = lineno;
        data['errorCol'] = colno;
        // data['url'] = source;
        data['costTime'] = 0;
        data['beginTime'] = _ROUTER_BEGINTIME;
        data['errorDetail'] = error.toString();
        yufp.monitor.pushLogs(data);
      }
    };

    // 加入请求过滤器
    yufp.service.addFilter({
      name: 'serviceMonitor',
      before: function (event) {
        if (yufp.monitor) {
          var monitorLoad = yufp.monitor.load;
          event.monitorCode = 'AJAX_' + +new Date() + ((Math.random() * 1000).toFixed(0) + '');
          monitorLoad && yufp.monitor.load({type: AJAX_TYPE_BEFORE, data: event});
        }
        return true;
      },
      after: function (event, oldEvent) {
        if (yufp.monitor) {
          var monitorLoad = yufp.monitor.load;
          event['monitorCode'] = oldEvent['monitorCode'];
          event['requestUrl'] = oldEvent['requestUrl'];
          monitorLoad && yufp.monitor.load({type: AJAX_TYPE_AFTER, data: event});
          delete event['monitorCode'];
          delete event['requestUrl'];
        }
        return true;
      },
      exception: function (event, oldEvent) {
        if (yufp.monitor) {
          var monitorLoad = yufp.monitor.load;
          event['monitorCode'] = oldEvent['monitorCode'];
          event['requestUrl'] = oldEvent['requestUrl'];
          monitorLoad && yufp.monitor.load({type: AJAX_TYPE_EXCEPTION, data: event});
          delete event['monitorCode'];
          delete event['requestUrl'];
        }
        return true;
      }
    }, true);

    // 添加路由过滤器
    yufp.router.addFilter({
      name: 'routerMonitor',
      before: function (code, data, cite) {
        if (yufp.monitor) {
          var monitorLoad = yufp.monitor.load;
          // 定义一个全局值
          _ROUTER_TRACEID = getTraceId();
          _ROUTER_BEGINTIME = new Date().getTime();
          monitorLoad && yufp.monitor.load({type: ROUTER_TYPE_BEFORE, data: yufp.clone({}, cite), code: code});
        }
        return true;
      },
      ready: function (exports, code, data, cite) {
        if (yufp.monitor) {
          var monitorLoad = yufp.monitor.load;
          monitorLoad && yufp.monitor.load({type: ROUTER_TYPE_READY, data: yufp.clone({}, cite), code: code});
        }
      },
      unMount: function (code, cite) {
        if (yufp.monitor) {
          var monitorLoad = yufp.monitor.load;
          monitorLoad && yufp.monitor.load({type: ROUTER_TYPE_UNMOUNT, data: yufp.clone({}, cite), code: code});
        }
      }
    });

    // Vue框架异常捕获
    /**
     * 指定组件的渲染和观察期间未捕获错误的处理函数
     * @param {object} error 错误信息。error.message 错误信息，error.stack: 代码执行栈
     * @param {*} vm 抛出异常的Vue实例
     * @param {*} info `info` 是 Vue 特定的错误信息，比如错误所在的生命周期钩子，只在 2.2.0+ 可用
     */
    var errorHandler = function (error, vm, info) {
      yufp.logger.error(error);
      var data = {};
      data = errorAnalysis(error, {});
      data.errorMsg = error.message;
      data.errorDetail = error.stack;
      yufp.monitor && yufp.monitor.pushLogs(data);
    };
    Vue.config.errorHandler = errorHandler;
  };
  /**
   * 加载事件，路由 before/路由 ready/路由 unMount
   * /request before/request after/requestexception
   * /页面内mounted后调用了此方法
   * @param {Object} setting
   */
  Monitor.prototype.load = function (setting) {
    var type = setting.type + '';
    // 路由加载信息
    if (type.indexOf('ROUTER-') > -1) {
      this.addRouterLog(setting);
    } else if (type.indexOf('AJAX-') > -1) {
      this.addAPILog(setting);
    } else {
      yufp.logger.info('数据监控丢失数据', type);
    }
  };
  /**
   * 获取初始化条件
   */
  Monitor.prototype.getInitParam = function (patchid) {
    // 调用接口获取初始化参数信息
  };
  /**
   * 合并数据
   */
  Monitor.prototype.mergeData = function (log) {
    if (!_BROWSER_FINGERPRINT) {
      _BROWSER_FINGERPRINT = yufp.tfp.getTFP();
    }
    if (!_CLIENT_IP) {
      getIp();
    }
    this.COMMON_DATA.traceId = _ROUTER_TRACEID;
    this.COMMON_DATA.beginTime = _ROUTER_BEGINTIME;
    this.COMMON_DATA.userId = yufp.session.userId;
    this.COMMON_DATA.browerMark = _BROWSER_FINGERPRINT;
    this.COMMON_DATA.ip = _CLIENT_IP;
    this.COMMON_DATA['logTimestamp'] = new Date().getTime();
    return yufp.extend({}, this.COMMON_DATA, getRouterInfo(), log);
  };
  /**
   * 添加路由日志
   * @param {object} setting 添加信息
   */
  Monitor.prototype.addRouterLog = function (setting) {
    var type = setting.type + '';
    var cite = setting.data || {};
    var tmp = {
      time: new Date().getTime(),
      data: cite,
      code: setting.code
    };

    // if (type.search(ROUTER_TYPE_MOUNT) !== -1) {
    //   // console.log(type, tmp);
    // }
    if (!this.TEMP_LOGS[type]) {
      this.TEMP_LOGS[type] = {};
    }
    var keyVaule = _ROUTER_TRACEID;
    this.TEMP_LOGS[type][keyVaule] = tmp;
    if (type === ROUTER_TYPE_BEFORE) {
      this.TEMP_LOGS[type][cite.el] = tmp;
    }
    // 发生时间,当传递的数据类型不为before操作时，做计算
    if (type !== ROUTER_TYPE_BEFORE) {
      var startStatus = this.TEMP_LOGS[ROUTER_TYPE_BEFORE];
      var endStatus = this.TEMP_LOGS[type];
      for (var key in endStatus) {
        if (!startStatus || !startStatus[key]) {
          continue;
        }
        var routeData = {};
        var time = endStatus[key].time - startStatus[key].time;
        // if (type.search(ROUTER_TYPE_READY) !== -1) {
        //   // 存储从before到ready的时间，为计算page mounted的耗时时长做准备
        //   // page mounted耗时 = 页面路由before~ready耗时 + 路由ready~页面内容挂载（即页面vue实例调用mounted方法）耗时
        //   this.TEMP_LOGS[type][cite.el].beforeInterval = time;
        // }
        if (endStatus && endStatus.data) {
          routeData.costTime = endStatus.data.startTime - endStatus.time;
        } else {
          routeData.costTime = type.search(ROUTER_TYPE_MOUNT) !== -1 ? time + startStatus[key].beforeInterval : time;
        }
        // routeData.eventName = type;
        var routeId = cite.id ? cite.id : startStatus[key].data.id || startStatus[key].code;
        routeId = routeId || setting.code;
        // 通过路由id取得路由信息
        var routeinfo = yufp.router.getRoute(routeId);
        if (routeinfo.html) {
          routeData.url = routeinfo.html.replace('.html', '');
        } else {
          yufp.logger.info('路由信息异常', cite, routeId, routeinfo, routeData);
        }
        routeData.urlType = 'html';
        routeData.ispv = true;
        routeData.code = 200;
        routeData.beginTime = _ROUTER_BEGINTIME;
        this.pushLogs(routeData);
        // 加载本次路由的静态资源信息
        this.analysisResourceData();
        delete this.TEMP_LOGS[type][key];
      }
    }
  };
  /**
   * 添加api请求日志
   * @param {object} setting api请求信息
   */
  Monitor.prototype.addAPILog = function (setting) {
    var type = setting.type + '';
    // 取得userId
    if (type.search('AJAX-') != -1) { // ajax请求数据
      var event = setting.data;
      var o = {
        // 请求结果成功或失败
        msg: event.message,
        data: this.updateUrl === event.requestUrl ? '' : JSON.stringify(event.data),
        time: new Date().getTime(),
        code: event.xhr && event.xhr.status,
        // 请求url
        url: event.requestUrl
      };
      if (checkAPIWhiteList(o.url)) {
        return false;
      }
      if (!this.TEMP_LOGS[type]) {
        this.TEMP_LOGS[type] = {};
      }

      // 唯一标识
      this.TEMP_LOGS[type][event.monitorCode] = o;
      if (type.search('BEFORE') === -1) {
        // 做计算并存储数据
        var start = this.TEMP_LOGS[AJAX_TYPE_BEFORE];
        var end = this.TEMP_LOGS[type];
        for (var k in end) {
          // 生成一条监控数据 --start
          var ajaxData = {};
          ajaxData.url = end[k].url;
          ajaxData.costTime = start ? end[k].time - (start[k] && start[k].time) : 0;
          // 请求状态
          if (end[k].code !== undefined) {
            ajaxData.code = parseInt(end[k].code);
          } else {
            ajaxData.code = end[k].msg == 'success' ? 200 : end[k].msg;
          }
          // ajaxData.eventName = type;
          if (type === AJAX_TYPE_EXCEPTION) {
            ajaxData.isError = true;
          }
          ajaxData.urlType = 'api';
          ajaxData.beginTime = (start && start[k] && start[k].time) || end[k].time;
          // 生成一条监控数据 --end
          this.pushLogs(ajaxData);
          // 计算后移除该项，避免下次做重复计算
          delete this.TEMP_LOGS[type][k];
          this.TEMP_LOGS[AJAX_TYPE_BEFORE] && delete this.TEMP_LOGS[AJAX_TYPE_BEFORE][k];
        }
      }
    }
  };
  /**
   * 卸载事件
   * @param {Object} setting
   */
  Monitor.prototype.unload = function (setting) {};
  /**
   * 处理批量日志上传
   */
  Monitor.prototype.patchUploadLogs = function () {
    _POST_LOGS_PATCHID = getTraceId();
    _POST_LOGS_TEMP[_POST_LOGS_PATCHID] = this._logs.splice(0, this._logs.length);
    this.updateLog(_POST_LOGS_PATCHID);
  };
  /**
   * 添加数据到数组中
   * @param {object} log 日志数据
   */
  Monitor.prototype.pushLogs = function (log) {
    this._logs.push(this.mergeData(log));
    // this.checkLogs();
  };
  /**
   * 校验日志数组长度
   */
  Monitor.prototype.checkLogs = function () {
    var len = this._logs.length;
    if (len >= this.maxLength && !_POST_LOGS_PATCHID) {
      this.patchUploadLogs();
    }
  };
  /**
   * 输出性能数据
   * @param {Object} perObj 性能数据对象
   */
  Monitor.prototype.outPutData = function (perObj) {
    var data = analysisTiming(perObj.timing, true);
    data.dataType = 'firstOpen';
    data.url = window.location.href;
    data.urlType = 'html';
    this.pushLogs(data);
  };
  /**
   * 分析资源数据
   */
  Monitor.prototype.analysisResourceData = function () {
    var allResource = window.performance.getEntriesByType('resource');
    var list = allResource.slice(_RESOURCE_INDEX);
    _RESOURCE_INDEX = allResource.length;
    var _this = this;
    list.forEach(function (item) {
      if (item.xmlhttprequest !== 'xmlhttprequest') {
        _this.pushLogs(analysisTiming(item));
      }
    });
  };
  /**
   * 数据满条件后调用接口上传数据
   */
  Monitor.prototype.updateLog = function (patchid) {
    var _this = this;
    var _tmplogs = _POST_LOGS_TEMP[patchid].slice(0, _POST_LOGS_LENGTH);
    // console.log('当前要提交的数据', _tmplogs.length, _POST_LOGS_TEMP);
    // console.log('当前要提交的数据', _tmplogs.length);
    // _tmplogs.forEach(function (item) {
    //   if (item.ispv === true) {
    //     console.log('pv数据', item);
    //   }
    // });
    if (_tmplogs.length > 0) {
      // 调用接口
      yufp.service.request({
        method: 'POST',
        prefixUrl: yufp.settings.monitorUrl,
        url: this.updateUrl,
        needToken: false,
        data: JSON.stringify(_tmplogs),
        callback: function (code, message, response) {
          if (code === 0) {
            // 删除已经上传的数据
            _POST_LOGS_TEMP[patchid].splice(0, _POST_LOGS_LENGTH);
            // 还有数据就继续上传，否则删除这个key值
            if (_POST_LOGS_TEMP[patchid].length > 0) {
              _this.updateLog(patchid);
            } else {
              delete _POST_LOGS_TEMP[patchid];
              if (Object.keys(_POST_LOGS_TEMP).length > 0) {
                _this.updateLog(Object.keys(_POST_LOGS_TEMP)[0]);
              } else {
                // 没有待上传数据了就清空
                _POST_LOGS_PATCHID = '';
                // _this.checkLogs();
              }
            }
          }
        }
      });
    }
  };
  /**
   * 校验api路径是否再白名单中
   * @param {string} apiUrl 待校验的api路径
   */
  function checkAPIWhiteList (apiUrl) {
    var isWhite = false;
    _API_WHITE_LIST.forEach(function (reg) {
      isWhite = reg.test(apiUrl);
      if (isWhite) {
        return isWhite;
      }
    });
    return isWhite;
  }
  /**
   * 获取clientID，当前js加载生成全局唯一
   */
  function getClientId () {
    return GUID_PREFIX + guid();
  }
  /**
   * 获取traceid,每次路由加载生成唯一
   */
  function getTraceId () {
    return GUID_PREFIX + '_router_' + guid();
  }
  /**
   * 获取静态数据
   */
  function getStaticData () {
    // 分辨率
    var navigator = window.navigator;
    var os = getOsInfo();
    var browers = getBrowerInfo();
    return {
      // 分辨率
      resolution: window.screen.width + '*' + window.screen.height,
      // 客户端ip
      ip: '',
      // 浏览器信息
      navigatorInfo: navigator.userAgent,
      // 操作系统信息
      system: os.name + os.version,
      // 浏览器名称
      browser: browers.type,
      // 浏览器版本  只取大版本号 modify by liucheng3 2019-10-30
      browerVersion: browers.version == 'Unknown' ? browers.version : browers.version.split('.')[0],
      // 应用名称
      // title: document.title,
      // 访问域名
      hostname: window.location.hostname,
      // 通信协议
      protocol: window.location.protocol.replace(':', '')
      // 当前请求地址
      // address: window.location.href
    };
  };
  /**
   * 计算guid值
   */
  function guid () {
    return 'xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
      var r = Math.random() * 16 | 0, v = c == 'x' ? r : r & 0x3 | 0x8;
      return v.toString(16);
    });
  }
  /**
   * 解析错误信息
   * @param {Object} error 错误对象
   * @param {Object} data 接受信息对象
   */
  function errorAnalysis (error, data) {
    var stacks = error.stack.split(/(\s){2,}/);
    for (var i = 0, len = stacks.length; i < len; i++) {
      var s = stacks[i];
      if (s.length > 0) {
        if (i === 0) {
          data.errorName = s;
          // 获取错误类型
          var errorTypes = s.split(':');
          data.errorType = errorTypes[0];
        }
        if (i > 1) {
          var file = s.split(/\((.+?)\)/g);
          if (file.length > 2) {
            var fs = file[1].split(/\?/);
            data.errorJsUrl = fs[0];
            var fss = fs[1].split(/:/);
            data.errorRow = fss[1];
            data.errorCol = fss[2];
          }
          break;
        }
      }
    }
    data['isError'] = true;
    data['beginTime'] = _ROUTER_BEGINTIME || new Date().getTime();
    data['costTime'] = _ROUTER_BEGINTIME;
    data['urlType'] = 'html';
    data['code'] = 200;
    return data;
  }
  /**
   * 获取当前菜单信息
   */
  function getRouterInfo () {
    var rData = {
      title: document.title
    };
    try {
      var tab = yufp.frame.getTab;
      if (tab) {
        tab = yufp.frame.getTab();
        return {
          menuId: tab.menuId,
          title: tab.title,
          url: tab.funcUrl || window.location.href
        };
      } else {
        tab = getRouterInfoOld();
        if (tab) {
          return {
            menuId: tab.menuId,
            title: tab.title,
            url: tab.url
          };
        }
      }
      return rData;
    } catch (error) {
      return rData;
    }
  }
  /**
   * 兼容老版本获取当前路由数据
   */
  function getRouterInfoOld () {
    var tab = yufp.frame.tab();
    if (tab) {
      var router = yufp.router.getRoute(tab.url);
      return {
        menuId: tab.key,
        title: tab.title,
        url: (router && router.html.replace('.html', '')) || window.location.href
      };
    } else {
      return null;
    }
  }
  /**
   * 获取操作系统信息
   */
  function getOsInfo () {
    var userAgent = navigator.userAgent.toLowerCase();
    var name = 'Unknown';
    var version = 'Unknown';
    if (userAgent.indexOf('win') > -1) {
      name = 'Windows';
      if (userAgent.indexOf('windows nt 5.0') > -1) {
        version = '2000';
      } else if (userAgent.indexOf('windows nt 5.1') > -1 || userAgent.indexOf('windows nt 5.2') > -1) {
        version = 'XP';
      } else if (userAgent.indexOf('windows nt 6.0') > -1) {
        version = 'Vista';
      } else if (userAgent.indexOf('windows nt 6.1') > -1 || userAgent.indexOf('windows 7') > -1) {
        version = '7';
      } else if (userAgent.indexOf('windows nt 6.2') > -1 || userAgent.indexOf('windows 8') > -1) {
        version = '8';
      } else if (userAgent.indexOf('windows nt 6.3') > -1) {
        version = '8.1';
      } else if (userAgent.indexOf('windows nt 6.2') > -1 || userAgent.indexOf('windows nt 10.0') > -1) {
        version = '10';
      } else {
        version = 'Unknown';
      }
    } else if (userAgent.indexOf('iphone') > -1) {
      name = 'Iphone';
    } else if (userAgent.indexOf('mac') > -1) {
      name = 'Mac';
    } else if (userAgent.indexOf('x11') > -1 || userAgent.indexOf('unix') > -1 || userAgent.indexOf('sunname') > -1 || userAgent.indexOf('bsd') > -1) {
      name = 'Unix';
    } else if (userAgent.indexOf('linux') > -1) {
      if (userAgent.indexOf('android') > -1) {
        name = 'Android';
      } else {
        name = 'Linux';
      }
    } else {
      name = 'Unknown';
    }
    return {
      name: name,
      version: version
    };
  }
  /**
   * 获取浏览器信息
   */
  function getBrowerInfo () {
    var Browser = (function () {
      var document = window.document,
        navigator = window.navigator,
        agent = navigator.userAgent.toLowerCase(),
        // IE8+支持.返回浏览器渲染当前文档所用的模式
        // IE6,IE7:undefined.IE8:8(兼容模式返回7).IE9:9(兼容模式返回7||8)
        // IE10:10(兼容模式7||8||9)
        IEMode = document.documentMode,
        // chorme
        chrome = window.chrome || false,
        System = {
          // user-agent
          agent: agent,
          // 是否为IE
          isIE: /trident/.test(agent),
          // Gecko内核
          isGecko: agent.indexOf('gecko') > 0 && agent.indexOf('like gecko') < 0,
          // webkit内核
          isWebkit: agent.indexOf('webkit') > 0,
          // 是否为标准模式
          isStrict: document.compatMode === 'CSS1Compat',
          // 是否支持subtitle
          supportSubTitle: function () {
            return 'track' in document.createElement('track');
          },
          // 是否支持scoped
          supportScope: function () {
            return 'scoped' in document.createElement('style');
          },

          // 获取IE的版本号
          ieVersion: function () {
            var rMsie = /(msie\s|trident.*rv:)([\w.]+)/;
            var ma = window.navigator.userAgent.toLowerCase();
            var match = rMsie.exec(ma);
            try {
              return match[2];
            } catch (e) {
              // console.log('error');
              return IEMode;
            }
          },
          // Opera版本号
          operaVersion: function () {
            try {
              if (window.opera) {
                return agent.match(/opera.([\d.]+)/)[1];
              } else if (agent.indexOf('opr') > 0) {
                return agent.match(/opr\/([\d.]+)/)[1];
              }
            } catch (e) {
              // console.log('error');
              return 0;
            }
          }
        };

      try {
        // 浏览器类型(IE、Opera、Chrome、Safari、Firefox)
        System.type = System.isIE ? 'IE'
          : window.opera || (agent.indexOf('opr') > 0) ? 'Opera'
            : agent.indexOf('chrome') > 0 ? 'Chrome'
            // safari也提供了专门的判定方式
              : window.openDatabase ? 'Safari'
                : agent.indexOf('firefox') > 0 ? 'Firefox'
                  : 'unknow';

        // 版本号
        System.version = System.type === 'IE' ? System.ieVersion()
          : System.type === 'Firefox' ? agent.match(/firefox\/([\d.]+)/)[1]
            : System.type === 'Chrome' ? agent.match(/chrome\/([\d.]+)/)[1]
              : System.type === 'Opera' ? System.operaVersion()
                : System.type === 'Safari' ? agent.match(/version\/([\d.]+)/)[1]
                  : '0';

        // 浏览器外壳
        System.shell = function () {
          if (agent.indexOf('edge') > 0) {
            System.version = agent.match(/edge\/([\d.]+)/)[1] || System.version;
            return 'edge浏览器';
          }
          // 遨游浏览器
          if (agent.indexOf('maxthon') > 0) {
            System.version = agent.match(/maxthon\/([\d.]+)/)[1] || System.version;
            return '傲游浏览器';
          }
          // QQ浏览器
          if (agent.indexOf('qqbrowser') > 0) {
            System.version = agent.match(/qqbrowser\/([\d.]+)/)[1] || System.version;
            return 'QQ浏览器';
          }

          // 搜狗浏览器
          if (agent.indexOf('se 2.x') > 0) {
            return '搜狗浏览器';
          }

          // Chrome:也可以使用window.chrome && window.chrome.webstore判断
          if (chrome && System.type !== 'Opera') {
            var external = window.external,
              clientInfo = window.clientInformation,
              // 客户端语言:zh-cn,zh.360下面会返回undefined
              clientLanguage = clientInfo.languages;

            // 猎豹浏览器:或者agent.indexOf('lbbrowser')>0
            if (external && 'LiebaoGetVersion' in external) {
              return '猎豹浏览器';
            }
            // 百度浏览器
            if (agent.indexOf('bidubrowser') > 0) {
              System.version = agent.match(/bidubrowser\/([\d.]+)/)[1] ||
                agent.match(/chrome\/([\d.]+)/)[1];
              return '百度浏览器';
            }
            // 360极速浏览器和360安全浏览器
            if (System.supportSubTitle() && typeof clientLanguage === 'undefined') {
              // object.key()返回一个数组.包含可枚举属性和方法名称
              var storeKeyLen = Object.keys(chrome.webstore).length,
                v8Locale = 'v8Locale' in window;
              return storeKeyLen > 1 ? '360极速浏览器' : '360安全浏览器';
            }
            return 'Chrome';
          }
          return System.type;
        };

        // 浏览器名称(如果是壳浏览器,则返回壳名称)
        System.name = System.shell();
        // 对版本号进行过滤过处理
        // System.version = System.versionFilter(System.version);
      } catch (e) {
        // console.log(e.message);
      }
      return {
        client: System
      };
    }());
    if (Browser.client.name === undefined || Browser.client.name === '') {
      Browser.client.name = 'Unknown';
      Browser.client.version = 'Unknown';
    } else if (Browser.client.version == undefined) {
      Browser.client.version = 'Unknown';
    }
    return Browser.client;
  }
  /**
   * 初始化相关数据分析
   * @param {Object} timing 开始事件
   */
  function analysisTiming (timing, isFirst) {
    var timeData = {
      // url: timing.name,
      resourceUrl: timing.name,
      // urlType: timing.initiatorType,
      urlType: 'html',
      domainLookupStart: timing.domainLookupStart,
      domainLookupEnd: timing.domainLookupEnd,
      dnsTime: timing.domainLookupEnd - timing.domainLookupStart,
      connectEnd: timing.connectEnd,
      connectStart: timing.connectStart,
      tcpTime: timing.connectEnd - timing.connectStart,
      responseEnd: timing.responseEnd,
      responseStart: timing.responseStart,
      contentTime: timing.responseEnd - timing.responseStart,
      domLoading: timing.domLoading,
      domInteractive: timing.domInteractive,
      // domTime: timing.domComplete - timing.domInteractive,
      domContentLoadedEventEnd: timing.domContentLoadedEventEnd,
      domContentLoadedEventStart: timing.navigationStart,
      domTime: timing.domContentLoadedEventEnd - timing.navigationStart,
      loadEventEnd: timing.loadEventEnd,
      loadEventStart: timing.loadEventStart,
      resourceTime: timing.loadEventEnd - timing.loadEventStart,
      requestStart: timing.requestStart,
      fetchStart: timing.fetchStart,
      beginTime: new Date().getTime(),
      'logTimestamp': new Date().getTime(),
      // 新增字段 by liucheng3 2019-10-23
      resLoadTime: timing.responseEnd - timing.fetchStart,
      interactiveTime: timing.domInteractive - timing.fetchStart,
      domDoneTime: timing.domContentLoadedEventEnd - timing.fetchStart,
      pageDoneTime: timing.loadEventEnd - timing.fetchStart
    };
    if (isFirst) {
      timeData['firstPaintTime'] = timing.responseEnd - timing.fetchStart;
    }
    timeData['costTime'] = timing.responseEnd - timing.fetchStart;
    return cleaningData(timeData);
  }
  /**
   * 清洗数据,将数据中的NaN清空
   */
  function cleaningData (data) {
    for (var k in data) {
      if (Object.prototype.toString.call(data[k]) === '[object Number]' && isNaN(data[k])) {
        data[k] = '';
      }
      if (Object.prototype.toString.call(data[k]) === '[object Undefined]') {
        data[k] = '';
      }
    }
    return data;
  }
  /**
   * 获取本机ip地址
   */
  function getIp () {
    var userAgent = window.navigator.userAgent;
    var isIE = userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1; // 判断是否IE<11浏览器
    var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf('rv:11.0') > -1;
    if (isIE || isIE11) {
    } else {
      // 在firefox44浏览器、opera63.0中可以得出结果
      // 在Google76上默认获取的是mdns，需要 将浏览器的 chrome://flags/#enable-webrtc-hide-local-ips-with-mdns flag设置为Disable
      // NOTE: window.RTCPeerConnection is 'not a constructor' in FF22/23
      var RTCPeerConnection = /* window.RTCPeerConnection || */ window.webkitRTCPeerConnection || window.mozRTCPeerConnection;

      if (RTCPeerConnection) {
        var _this = this;
        (function () {
          var rtc = new RTCPeerConnection({
            iceServers: []
            // update by taoting1 at 20190821 14:21
            // iceServers: [ {urls: 'stun:stun.services.mozilla.com'} ]
          });
          if (1 || window.mozRTCPeerConnection) { // FF [and now Chrome!] needs a channel/stream to proceed
            rtc.createDataChannel('', {
              reliable: false
            });
          };

          rtc.onicecandidate = function (evt) {
            // convert the candidate to SDP so we can run it through our general parser
            // see https://twitter.com/lancestout/status/525796175425720320 for details
            if (evt.candidate) {
              grepSDP('a=' + evt.candidate.candidate);
            }
          };
          rtc.createOffer(function (offerDesc) {
            grepSDP(offerDesc.sdp);
            rtc.setLocalDescription(offerDesc);
          }, function (e) {
            console.warn('offer failed', e);
          });

          var addrs = Object.create(null);
          addrs['0.0.0.0'] = false;

          function updateDisplay (newAddr) {
            if (newAddr in addrs) {
              return;
            } else {
              addrs[newAddr] = true;
            }
            var displayAddrs = Object.keys(addrs).filter(function (k) {
              return addrs[k];
            });
            _CLIENT_IP = displayAddrs.join(' or perhaps ') || 'n/a';
          }

          function grepSDP (sdp) {
            var hosts = [];
            sdp.split('\r\n').forEach(function (line) { // c.f. http://tools.ietf.org/html/rfc4566#page-39
              if (~line.indexOf('a=candidate')) { // http://tools.ietf.org/html/rfc4566#section-5.13
                var parts = line.split(' '), // http://tools.ietf.org/html/rfc5245#section-15.1
                  addr = parts[4],
                  type = parts[7];
                if (type === 'host') {
                  updateDisplay(addr);
                }
              } else if (~line.indexOf('c=')) { // http://tools.ietf.org/html/rfc4566#section-5.7
                var parts = line.split(' '),
                  addr = parts[2];
                updateDisplay(addr);
              }
            });
          }
        }());
      } else {
        yufp.logger.warn('<code>ifconfig | grep inet | grep -v inet6 | cut -d\' \' -f2 | tail -n1</code>');
      }
    }
  }
  var m = new Monitor();
  m.init();
  return m;
}));
